package il.technion.cs236369.parsers;

import il.technion.cs236369.common.Common;
import il.technion.cs236369.exceptions.ParsingException;
import il.technion.cs236369.interfaces.ITSPWriter;

import java.io.InputStream;
import java.util.Arrays;
import java.util.Scanner;

import javax.tools.Diagnostic;
import javax.tools.Diagnostic.Kind;
import javax.tools.DiagnosticListener;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.SimpleJavaFileObject;
import javax.tools.StandardJavaFileManager;

/**
 * A TSP files parser
 * 
 * @author Gal Lalouche
 * 
 */
public class TSPParser extends Parser<ITSPWriter> {

	public static final String				CLASS_NAME	= "TSPWriterImpl";

	private final JavaCompiler				compiler;
	private final StandardJavaFileManager	manager;
	private static Long						counter		= 0L;
	private long							count		= 0;

	public TSPParser(JavaCompiler compiler, StandardJavaFileManager manager) {
		this.compiler = compiler;
		this.manager = manager;
	}

	@Override
	public ITSPWriter parse(InputStream stream) throws ParsingException {
		Scanner scanner = null;
		try {
			synchronized (counter) {
				count = counter;
				counter++;
			}

			boolean javaMode = false;
			StringBuilder sb = new StringBuilder();
			scanner = new Scanner(stream);
			scanner.useDelimiter("<\\?");
			// java code
			sb.append("public class " + getCurrentClassName() + " implements il.technion.cs236369.interfaces.ITSPWriter {\n");
			sb.append("\tpublic void writeTSP(java.util.Map<String, String> params, java.io.PrintStream out, il.technion.cs236369.interfaces.ISession session) {\n");
			checkNullParam(sb, "params");
			checkNullParam(sb, "out");

			if (scanner.hasNext()) {
				String next = scanner.next();
				if (next.endsWith("?>")) {
					javaMode = handleJava(next, scanner, sb);
				} else {
					javaMode = handleHTML(next, scanner, sb);
				}
			}
			while (scanner.hasNext()) {
				String next = scanner.next();
				if (javaMode) {
					javaMode = handleJava(next, scanner, sb);
				} else {
					javaMode = handleHTML(next, scanner, sb);
				}
			}

			// close the class
			sb.append("\t\n}");
			sb.append("\n}");

			Common.getOut().println("\n\n----------------------------------\n\n");
			//		Common.getOut().println(sb + "");

			return compileSourceCode(sb.toString());
		} finally {
			if (scanner != null) {
				scanner.close();
			}
		}
	}

	private boolean handleHTML(String htmlCode, Scanner scanner, StringBuilder sb) {
		boolean javaMode;
		// reading html code
		htmlCode = htmlCode.replace("?>", "").replace("\r\n", "\"+\r\n" + "\"");
		if (htmlCode.trim().isEmpty() == false) {
			sb.append("\t\tout.print(\"" + htmlCode + "\");\n");
			Common.getOut().println("#" + htmlCode);
		}
		scanner.useDelimiter("\\?>");
		javaMode = true;
		return javaMode;
	}

	private boolean handleJava(String javaCode, Scanner scanner, StringBuilder sb) {
		boolean javaMode;
		// reading clean java code
		javaCode = javaCode.replace("<?", "");
		scanner.useDelimiter("<\\?");
		sb.append("\t\t" + javaCode + "\n");
		Common.getOut().println("$" + javaCode);
		javaMode = false;
		return javaMode;
	}

	private void checkNullParam(StringBuilder sb, String param) {
		sb.append("\t\tif (" + param + " == null) {\n\t\t\t throw new IllegalArgumentException(\"" + param + " is null\");\n\t\t }\n");
	}

	/**
	 * Compiles the sources code and returns a new
	 * 
	 * @param source The source code
	 * @return a new ITSPWriter instance
	 * @throws ParsingException If there was a problem with compiling the source
	 *             code
	 */
	private ITSPWriter compileSourceCode(String source) throws ParsingException {
		//		Common.getOut().println(source);
		SimpleJavaFileObject sourceCode = new JavaSourceFromString(getCurrentClassName(), source);
		Iterable<? extends JavaFileObject> units = Arrays.asList(sourceCode);
		Boolean status = compiler.getTask(null, manager, new DiagnosticListener<JavaFileObject>() {
			@Override
			public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
				// ignore warnings
				if (diagnostic.getKind() == Kind.WARNING) {
					return;
				}
				System.err.println(diagnostic.toString());
			}
		}, Arrays.asList("-d", "bin"), null, units).call();
		if (status == null || !status.booleanValue()) {
			Common.getOut().println("Compilation failed");
			throw new ParsingException("Compilation failed");
		} else {
			Common.getOut().printf("Compilation successful!!!\n");
		}

		try {
			Class<?> clazz = manager.getClassLoader(javax.tools.StandardLocation.CLASS_PATH).loadClass(getCurrentClassName());
			return (ITSPWriter)clazz.newInstance();
		} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
			throw new RuntimeException(e);
		}
	}

	private String getCurrentClassName() {
		return CLASS_NAME + count;
	}
}
